/*
 * Copyright (C) 2011 : Gilles Coomans 
 * 
 * This file is part of QuintEssence.
 * QuintEssence is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * QuintEssence is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with QuintEssence.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.exprom.qe.fields;
import haxe.Timer;
import net.exprom.qe.fields.QEField;
//import net.exprom.qe.IQELayerable;
//import net.exprom.qe.QEModel;
import net.exprom.qe.QEStack;

/**
 * ...
 * @author Gilles Coomans
 */

typedef QEModelListChild = { 
	modelStack:QEStack,
	metasStack:QEStack,
	collisionValue:String
};

class QENodeList extends QENode
{
	public var flattenXMLNode:Bool;
	public var colliderHash:Hash<Dynamic>;
	public var childs:Array<QEModelListChild>;
	//public var collideOn:String;	// pas de collision par défaut : il faut la spécifier. Valable que pour un node array. Si spécifié : l'array doit être "typé" (au moins ce champ là). Indique qu'il faut créer une hash en interne sur le champ de collision. Le champ de collision doit etre unique.
	
	public function new(name:String, parent:QEStack, params:Dynamic = null) 
	{
		super(name, parent, params);
	}
	
	override public function applyParameters(params:Dynamic, up:Bool = true, commonOnly:Bool = false):Void 
	{
		super.applyParameters(params, up, commonOnly);
		if (up)
		{
			if(Reflect.hasField(params, "flattenXMLNode"))
				parameters.flattenXMLNode = params.flattenXMLNode;
			if(Reflect.hasField(params, "collideOn"))
				parameters.collideOn = params.collideOn;
		}
	}
	
	override private function createParamStruct():Dynamic
	{
		fieldType = QEFT_NODE_LIST;
		getModelStack();
		childs = [];
		colliderHash = new Hash();
		parameters = {
			visibility:QEFV_PUBLIC, 
			fixAfterValuation:true, 
			clonable:true, 
			removable:true,
			finalRequired:false,
			layerRequired:false, 
			customCall: { },
			finaliseMetas:false,
			flattenXMLNode:false,
			collideOn:null
		};
		return parameters;
	}

	
	override public function setNamespace(namespace:String, name:String = null):Void
	{
		super.setNamespace(namespace, name);	
		for (c in childs) {
			if (c.metasStack != null)
				c.metasStack.setNamespace(path);
			c.modelStack.setNamespace(path);
		}
	}
	
	override public function setParent(parent:QEStack):Void
	{
		super.setParent(parent);
		for (c in childs) {
			if (c.metasStack != null)
				c.metasStack.setParent (parent);
			if(c.modelStack != null)
				c.modelStack.setParent(parent);
		}
	}
	
	private function createChild():Dynamic
	{
		var newObj = { modelStack:new QEStack(path, "itemModelStack"), metasStack:null, collisionValue:null };
		newObj.modelStack.rootContext = this.rootContext;
		newObj.modelStack.setParent(this.parent);
		newObj.modelStack.applyStack(modelStack);
		if (hasSettedMetas())
		{
			newObj.metasStack = new QEStack(path, "itemMetasStack");
			newObj.metasStack.IS_METAS = true;
			newObj.metasStack.rootContext = this.rootContext;
			newObj.metasStack.setParent (this.parent);
			newObj.metasStack.applyStack(metasStack);
		}
		childs.push(newObj);
		return newObj;
	}

	private function collideLayerList(list:Array<Dynamic>, up:Bool = true, isMetas:Bool = false, commonOnly:Bool = false):Void
	{
		#if QEC_DEBUG message("collideLayerList : isMetas : " + isMetas); #end
		//message("collideLayerList : isMetas : " + isMetas + " - list : " + Std.string(list));
		for (fStruct in list) 
		{ 
			if (parameters.collideOn != null && Reflect.hasField(fStruct, parameters.collideOn))
			{
				var key = Std.string(Reflect.field(fStruct, parameters.collideOn));
				var collided:QEModelListChild = colliderHash.get(key);
				if (collided == null)
				{
					if (commonOnly)
						continue;
					collided = createChild();
					colliderHash.set(key, collided);
					collided.collisionValue = key;
				}
				
				if(isMetas)
				{
					if (collided.metasStack == null)
					{
						collided.metasStack = new QEStack(path, "itemMetasStack");
						collided.metasStack.IS_METAS = true;
						collided.metasStack.rootContext = this.rootContext;
						collided.metasStack.setParent( this.parent );
						collided.metasStack.applyStack(metasStack);
					}
					collided.metasStack.applyLayer(fStruct, up, commonOnly);
				}
				else
					collided.modelStack.applyLayer(fStruct, up, commonOnly);
			}
			else if(!commonOnly)
			{
				var ch:QEModelListChild = createChild();
				if(isMetas)
				{
					if (ch.metasStack == null)
					{
						ch.metasStack = new QEStack(path, "itemMetasStack");
						ch.metasStack.IS_METAS = true;
						ch.metasStack.rootContext = this.rootContext;
						ch.metasStack.setParent(this.parent);
						ch.metasStack.applyStack(metasStack);
					}
					ch.metasStack.applyLayer(fStruct, up, commonOnly);
				}
				else
					ch.modelStack.applyLayer(fStruct, up, commonOnly);
			}
		}	
	}
	
	override public function compil():Void 
	{
		super.compil();
		for (c in childs)
		{
			if(c.modelStack != null)
				c.modelStack.compil();
			if (c.metasStack != null)
				c.metasStack.compil();
		}
	}
	
	override public function compilField(field:QEField, up:Bool = true, commonOnly:Bool = false):Void 
	{
		super.compilField(field, up, commonOnly);
		var f:QENodeList = cast(field, QENodeList);
		if (f.parameters.collideOn != this.parameters.collideOn)
			throw "Collider need to be the same when compiling two NodeList field. aborting";
		for (fch in f.childs) 
		{
			if(fch.collisionValue != null)
			{
				var collided = colliderHash.get(fch.collisionValue);
				if (collided == null)
				{
					if (commonOnly)
						continue;
					collided = createChild();
					colliderHash.set(fch.collisionValue, collided);
					collided.collisionValue = fch.collisionValue;
				}
				if(hasSettedModel())
					collided.modelStack.applyStack(fch.modelStack, up, commonOnly);
				if(hasSettedMetas())
					collided.metasStack.applyStack(fch.metasStack, up, commonOnly);
			}
			else if(!commonOnly)
			{
				var ch = createChild();
				if(hasSettedModel())
					ch.modelStack.applyStack(fch.modelStack, up);
				if(hasSettedMetas())
					ch.metasStack.applyStack(fch.metasStack, up);
			}
		}	
	}
	
	override public function applyMetasLayer(layer:Dynamic, up:Bool = true, commonOnly:Bool = false):Void 
	{
		if (layer == null)
		{
			resetLeafs();
			return;
		}
		if(!Std.is(layer, Array))
			throw "QENodeList.applyLayer failed : Could not apply something different of an array on a ModelList for : " + path;
		var arr:Array<Dynamic> = layer;
		collideLayerList(arr, up, true, commonOnly);
	}
	
	override public function applyLayer(layer:Dynamic, up:Bool = true, commonOnly:Bool = false):Void 
	{
		if (layer == null)
		{
			resetLeafs();
			return;
		}
		if(!Std.is(layer, Array))
			throw "QENodeList.applyLayer failed : Could not apply something different of an array on a ModelList for : " + path;
		var arr:Array<Dynamic> = layer;
		collideLayerList(arr, up, false, commonOnly);
	}
	
	
	override public function clone(cloned:Dynamic = null):Dynamic 
	{
		if (cloned == null)
			cloned = new QENodeList(name, null);
		return super.clone(cloned);
	}
	
	override public function finalStruct(includeMetas:Bool = false, fixEvaluation:Bool = false, validateLeafs:Bool = false, includeDefaults:Bool = false):Dynamic 
	{
		//var time:Float = Timer.stamp();
		#if QEC_DEBUG message("finalStruct()"); #end
		var o = { _METAS: {}};
		Reflect.setField(o, name, null);
		Reflect.setField(o._METAS, name, null);
		
		if (hasSettedModel() && parameters.visibility == QEFV_PUBLIC)
			modelStack.compil();
		if (includeMetas && hasSettedMetas())
			metasStack.compil();
		
		if (childs.length == 0)
			return o;
	
		var metaArray:Array<Dynamic> = [];
		var arr:Array<Dynamic> = [];
		for (c in childs) 
		{
			if (c.metasStack != null)
			{
				//message("produce child metas stack");
				var metaFinal = c.metasStack.finalStruct( includeMetas, fixEvaluation, validateLeafs, includeDefaults);
				if (metaFinal != null)
					metaArray.push(metaFinal);
			}
			if(parameters.visibility == QEFV_PUBLIC && c.modelStack != null)
			{
				var modelFinal = c.modelStack.finalStruct( includeMetas, fixEvaluation, validateLeafs, includeDefaults);
				//message("produce child model stack : "+Std.string(modelFinal));
				if (modelFinal != null)
					arr.push(modelFinal);
			}
		}
		if (metaArray.length > 0)
			Reflect.setField(o._METAS, name, metaArray);
		if (arr.length > 0 && parameters.visibility == QEFV_PUBLIC) 
			Reflect.setField(o, name, arr);
		//message("output time (ms) : " + ((Timer.stamp() - time)*1000));
		return o;
	}
	
	
	override public function fixEvaluables():Void 
	{
		if (evaluated)
			return;
		if (hasSettedModel())
			getModelStack().fixEvaluables();
		if (hasSettedMetas())
			getMetasStack().fixEvaluables();
		for (c in childs)
		{
			if (c.modelStack != null)
				c.modelStack.fixEvaluables();
			if (c.metasStack != null)
				c.metasStack.fixEvaluables();
		}
		evaluated = true;
	}
	
	override public function validate():Bool
	{
		var ok = super.validate();
		for (c in childs)
		{
			if(c.modelStack != null)
				ok = ok && c.modelStack.validate();
			if(c.metasStack != null)
				ok = ok && c.metasStack.validate();
		}
		return ok;
	}
	
	public function getChild(childCollider:String):QEModelListChild
	{
		return colliderHash.get(childCollider);
	}
}